package jamirr;

import java.io.Serializable;
import java.util.*;
import java.util.function.BiFunction;
import java.util.function.Function;

abstract class AnnotatedElementBuilder<B extends AnnotatedElementBuilder<B>> {

    protected List<AnnotationMirror> declaredAnnotations;

    @SuppressWarnings("unchecked")
    public B declaredAnnotation(String name, Object... input) {
        Objects.requireNonNull(name, "name cannot be null");
        if (input != null) {
            if (declaredAnnotations == null) {
                declaredAnnotations = new ArrayList<>();
            }
            AnnotationMirrorBuilder builder = AnnotationMirrorBuilder.load(name);
            if (builder == null) {
                throw new IllegalStateException("AnnotationMirror for %s not found".formatted(name));
            }
            declaredAnnotations.add(builder.build(new MapN<>(input)));
        }
        return (B) this;
    }


    // ---------- Map Implementations ----------
    private static final long SALT32L;
    private static final boolean REVERSE;
    private static final int EXPAND_FACTOR = 2;

    static {
        long color = 0x243F_6A88_85A3_08D3L; // slice of pi
        long seed = System.nanoTime();
        SALT32L = (int) ((color * seed) >> 16) & 0xFFFF_FFFFL;
        // use the lowest bit to determine if we should reverse iteration
        REVERSE = (SALT32L & 1) == 0;
    }

    private abstract static class AbstractImmutableMap<K, V> extends AbstractMap<K, V> implements Serializable {

        @Override
        public void clear() {
            throw uoe();
        }

        @Override
        public V compute(K key, BiFunction<? super K, ? super V, ? extends V> rf) {
            throw uoe();
        }

        @Override
        public V computeIfAbsent(K key, Function<? super K, ? extends V> mf) {
            throw uoe();
        }

        @Override
        public V computeIfPresent(K key, BiFunction<? super K, ? super V, ? extends V> rf) {
            throw uoe();
        }

        @Override
        public V merge(K key, V value, BiFunction<? super V, ? super V, ? extends V> rf) {
            throw uoe();
        }

        @Override
        public V put(K key, V value) {
            throw uoe();
        }

        @Override
        public void putAll(Map<? extends K, ? extends V> m) {
            throw uoe();
        }

        @Override
        public V putIfAbsent(K key, V value) {
            throw uoe();
        }

        @Override
        public V remove(Object key) {
            throw uoe();
        }

        @Override
        public boolean remove(Object key, Object value) {
            throw uoe();
        }

        @Override
        public V replace(K key, V value) {
            throw uoe();
        }

        @Override
        public boolean replace(K key, V oldValue, V newValue) {
            throw uoe();
        }

        @Override
        public void replaceAll(BiFunction<? super K, ? super V, ? extends V> f) {
            throw uoe();
        }

        @Override
        public V getOrDefault(Object key, V defaultValue) {
            V v;
            return ((v = get(key)) != null)
                    ? v
                    : defaultValue;
        }
    }

    protected static final class MapN<K, V> extends AbstractImmutableMap<K, V> {

        final Object[] table; // pairs of key, value
        final int size; // number of pairs

        @SuppressWarnings("unchecked")
        MapN(Object... input) {
            if ((input.length & 1) != 0) { // implicit null-check of input
                throw new InternalError("length is odd");
            }
            size = input.length >> 1;

            int len = EXPAND_FACTOR * input.length;
            len = (len + 1) & ~1; // ensure table is even length
            table = new Object[len];

            for (int i = 0; i < input.length; i += 2) {
                K k = Objects.requireNonNull((K) input[i]);
                V v = Objects.requireNonNull((V) input[i + 1]);
                int idx = probe(k);
                if (idx >= 0) {
                    throw new IllegalArgumentException("duplicate key: " + k);
                } else {
                    int dest = -(idx + 1);
                    table[dest] = k;
                    table[dest + 1] = v;
                }
            }
        }

        @Override
        public boolean containsKey(Object o) {
            Objects.requireNonNull(o);
            return size > 0 && probe(o) >= 0;
        }

        @SuppressWarnings("all")
        @Override
        public boolean containsValue(Object o) {
            Objects.requireNonNull(o);
            for (int i = 1; i < table.length; i += 2) {
                Object v = table[i];
                if (v != null && o.equals(v)) {
                    return true;
                }
            }
            return false;
        }

        @Override
        public int hashCode() {
            int hash = 0;
            for (int i = 0; i < table.length; i += 2) {
                Object k = table[i];
                if (k != null) {
                    hash += k.hashCode() ^ table[i + 1].hashCode();
                }
            }
            return hash;
        }

        @Override
        @SuppressWarnings("unchecked")
        public V get(Object o) {
            if (size == 0) {
                Objects.requireNonNull(o);
                return null;
            }
            int i = probe(o);
            if (i >= 0) {
                return (V) table[i + 1];
            } else {
                return null;
            }
        }

        @Override
        public int size() {
            return size;
        }

        @Override
        public boolean isEmpty() {
            return size == 0;
        }

        class MapNIterator implements Iterator<Entry<K, V>> {

            private int remaining;
            private int idx;

            MapNIterator() {
                remaining = size;
                // pick an even starting index in the [0 .. table.length-1]
                // range randomly based on SALT32L
                idx = (int) ((SALT32L * (table.length >> 1)) >>> 32) << 1;
            }

            @Override
            public boolean hasNext() {
                return remaining > 0;
            }

            private int nextIndex() {
                int idx = this.idx;
                if (REVERSE) {
                    if ((idx += 2) >= table.length) {
                        idx = 0;
                    }
                } else {
                    if ((idx -= 2) < 0) {
                        idx = table.length - 2;
                    }
                }
                return this.idx = idx;
            }

            @SuppressWarnings("unchecked")
            @Override
            public Entry<K, V> next() {
                if (remaining > 0) {
                    int idx;
                    //noinspection StatementWithEmptyBody
                    while (table[idx = nextIndex()] == null) {
                    }
                    Entry<K, V> e =
                            new KeyValueHolder<>((K) table[idx], (V) table[idx + 1]);
                    remaining--;
                    return e;
                } else {
                    throw new NoSuchElementException();
                }
            }
        }

        @Override
        public Set<Entry<K, V>> entrySet() {
            return new AbstractSet<>() {
                @Override
                public int size() {
                    return MapN.this.size;
                }

                @Override
                public Iterator<Entry<K, V>> iterator() {
                    return new MapNIterator();
                }
            };
        }

        // returns index at which the probe key is present; or if absent,
        // (-i - 1) where i is location where element should be inserted.
        // Callers are relying on this method to perform an implicit null-check
        // of pk.
        @SuppressWarnings("unchecked")
        private int probe(Object pk) {
            int idx = Math.floorMod(pk.hashCode(), table.length >> 1) << 1;
            while (true) {
                K ek = (K) table[idx];
                if (ek == null) {
                    return -idx - 1;
                } else if (pk.equals(ek)) {
                    return idx;
                } else if ((idx += 2) == table.length) {
                    idx = 0;
                }
            }
        }
    }

    private static final class KeyValueHolder<K, V> implements Map.Entry<K, V> {
        final K key;
        final V value;

        KeyValueHolder(K k, V v) {
            key = Objects.requireNonNull(k);
            value = Objects.requireNonNull(v);
        }

        @Override
        public K getKey() {
            return key;
        }

        @Override
        public V getValue() {
            return value;
        }

        @Override
        public V setValue(V value) {
            throw new UnsupportedOperationException("not supported");
        }

        @Override
        public boolean equals(Object o) {
            return o instanceof Map.Entry<?, ?> e
                    && key.equals(e.getKey())
                    && value.equals(e.getValue());
        }

        @Override
        public int hashCode() {
            return key.hashCode() ^ value.hashCode();
        }

        @Override
        public String toString() {
            return key + "=" + value;
        }
    }

    private static UnsupportedOperationException uoe() {
        return new UnsupportedOperationException();
    }
}
